module StochasticSimulation.ChemicalReaction.ChemicalReaction

open StochasticSimulation.ChemicalReaction.ChemicalSpecies
open System.Collections.Generic
open System;

// calculate x * (x-1) * (x-2) *... (x-n+1 )
let calcCombinations n x = 
    if x = 0 then
        0
    else
        let i = 1
        let rec innerCalc i y =
                 if  i < n then
                     innerCalc (i+1) (y*(x-i))
                 else
                     y
        innerCalc i x

type SpeciesChanges = { Destroyed : int ; Created : int }

let UpdateSystem speciesCounts (changes : IDictionary<Species,SpeciesChanges>) =
    if changes.ContainsKey(speciesCounts.Species) then
        { speciesCounts with Count = speciesCounts.Count - changes.[speciesCounts.Species].Destroyed + changes.[speciesCounts.Species].Created }
    else
        speciesCounts

[<NoComparison>]    
[<CustomEquality>]    
type Reaction = 
    { Rate : float ; Changes : IDictionary<Species,SpeciesChanges> }
    member this.React system =
        Seq.map (fun x -> UpdateSystem x this.Changes) system
        
    member this.Propensity system = 
        let speciesInReaction = Seq.choose (fun s -> if this.Changes.ContainsKey(s.Species) then Some(s) else None ) system
        let counts = 
            speciesInReaction 
            |> Seq.map ( fun s -> ( this.Changes.[s.Species].Destroyed, s ) )
            |> Seq.filter (fun (n,x) -> n <> 0) 
            |> Seq.fold ( fun acc (n,x) ->  acc * (calcCombinations n x.Count) ) 1
        let propensity = float counts * this.Rate
        propensity
        
    member this.Affects =
        this.Changes 
            |> Seq.filter ( fun c -> not (c.Value.Created = c.Value.Destroyed ) )
            |> Seq.map (fun c -> c.Key )
            |> Seq.toList

    member this.Depends =
        this.Changes
            |> Seq.filter (fun c -> not (c.Value.Destroyed = 0 ) )
            |> Seq.map (fun c -> c.Key)
            |> Seq.toList
    
    override this.Equals( o : obj) =
        match o with
            | :? Reaction as other -> Seq.zip other.Changes.Keys this.Changes.Keys |> Seq.fold (fun a (l,r) -> a && (l = r && other.Changes.[l] = this.Changes.[r]) ) true
            | _ -> false
            
    override this.GetHashCode() = this.Changes.GetHashCode()
        
    override this.ToString() =
        Seq.fold (fun acc s -> acc + String.Format("{0}[{1},{2}];",s.Name, this.Changes.[s].Destroyed.ToString(), this.Changes.[s].Created.ToString() ) ) "" this.Changes.Keys
            
type ReactionResult = { tau : float ; UpdatedSystem : seq<SpeciesCount> }